
#include "../main.h"

#include <prlink.h>
#include <libgen.h>
#include <sys/param.h>


#include <js/jsapi.h>

#define CLASS Global


void clear_timeout_thread_proc(void *id){
//	close_js_thread((char *)id-(char *)0);
}


//////////////////////////////////////////////////////////////////////////////
//
static JSBool
global_clearTimeout(JSContext *cx, uintN argc, jsval *vp) {
	int id;
	if(!JS_ConvertArguments(cx, 1, JS_ARGV(cx, vp)+1, "i", &id))
		return JS_FALSE;

//	close_js_thread(id);
	
/*
	PR_CreateThread(
		PR_USER_THREAD,
		clear_timeout_thread_proc,
		(void *)((char *)0+id),
		PR_PRIORITY_NORMAL,
		PR_LOCAL_THREAD,
		PR_UNJOINABLE_THREAD,
		0
	);
*/	
	//close_js_thread(id);
	JS_SET_RVAL(cx, vp, JSVAL_VOID);
	return JS_TRUE;
}


//////////////////////////////////////////////////////////////////////////////
//
static JSBool
global_setTimeout(JSContext *cx, uintN argc, jsval *vp) {
	trace1("global_setTimeout()");
	
	js_thread_t *x = get_thread_slot(NULL);

	
	JSBool ok;

	jsval *argv = JS_ARGV(cx, vp);

	
	JS_BeginRequest(cx);
	ok = JS_ConvertValue(cx, argv[0], JSTYPE_FUNCTION, &x->func);
	JS_EndRequest(cx);

	if (!ok) {
		JS_SET_RVAL(cx, vp, JSVAL_VOID);
		return JS_TRUE;
	}

	if ((argc > 1) && !JS_ConvertArguments(cx, 1, argv + 1, "i", &x->delay))
		x->delay = 0;

	//JS_EndRequest(cx);

	//JS_AddRoot(cx, &x->func);

	unsigned long id = x->id;

	PRThread *thread = PR_CreateThread(
		PR_USER_THREAD,
		js_thread_proc,
		(void *)x,
		PR_PRIORITY_NORMAL,
		PR_LOCAL_THREAD,
		//PR_JOINABLE_THREAD,
		PR_UNJOINABLE_THREAD,
		0
	);
	if (!thread) {
		trace2("PR_CreateThread() failed");
		fatal("PR_CreateThread() failed");
	}
	
	JS_SET_RVAL(cx, vp, INT_TO_JSVAL(id));
	return JS_TRUE;
}


/**
 *
 */
static JSBool
global_getcwd(JSContext *cx, uintN argc, jsval *vp) {
	char *buf = (char *)malloc(MAXPATHLEN + 1);
	getcwd(buf, MAXPATHLEN);
	jsval rval = atov(cx, buf);
	free(buf);
	JS_SET_RVAL(cx, vp, rval);
	return JS_TRUE;
}



/**
 *
 */
static JSBool
global_basename(JSContext *cx, uintN argc, jsval *vp) {
	char *str;
	JSBool ok = JS_ConvertArguments(cx, argc, JS_ARGV(cx, vp), "s", &str);	
	if (!ok) return JS_FALSE;
	char *out = basename(str);
	jsval rval = atov(cx, out);
	free(out);
	JS_SET_RVAL(cx, vp, rval);
	return JS_TRUE;
}



/**
 *
 */
static JSBool
global_dirname(JSContext *cx, uintN argc, jsval *vp) {
	char *s;
	JSBool ok = JS_ConvertArguments(cx, argc, JS_ARGV(cx, vp), "s", &s);
	if (!ok) return JS_FALSE;
	char *out = dirname(s);
	jsval rval = atov(cx, out);
	free(out);
	free(s);
	JS_SET_RVAL(cx, vp, rval);
	return JS_TRUE;
}


/**
 *
 */
static JSBool
global_chdir(JSContext *cx, uintN argc, jsval *vp) {
	char *s;
	JSBool ok = JS_ConvertArguments(cx, argc, JS_ARGV(cx, vp), "s", &s);	
	if (!ok) return JS_FALSE;
	if (0 != chdir(s))
		JS_SET_RVAL(cx, vp, JSVAL_FALSE);
	else
		JS_SET_RVAL(cx, vp, JSVAL_TRUE);
	free(s);
	return JS_TRUE;
}



//PRLock *global_print_lock = NULL;

//////////////////////////////////////////////////////////////////////////////
//
static JSBool
global_print(JSContext *cx, uintN argc, jsval *vp) {
	char *str;

#if JS_VERSION > 180	
	jsval *argv = JS_ARGV(cx, vp);
	JSString *s = JS_ValueToString(cx, argv[0]);
	str = JS_EncodeString(cx, s);
#else
	JSBool ok = JS_ConvertArguments(cx, argc, JS_ARGV(cx, vp), "s", &str);	
	if (!ok) return JS_FALSE;
#endif
	
	
jsrefcount rc = JS_SuspendRequest(cx);
	console_set_lock(1);
	printf("%s\n",  str);
	fflush(stdout);
	console_set_lock(0);
JS_ResumeRequest(cx, rc);

	JS_SET_RVAL(cx, vp, JSVAL_VOID);
	return JS_TRUE;
}


//////////////////////////////////////////////////////////////////////////////
//
static JSBool
global_trace(JSContext *cx, uintN argc, jsval *vp) {
	char *str;
	if (!JS_ConvertArguments(cx, argc, JS_ARGV(cx, vp), "s", &str))
		return JS_FALSE;
jsrefcount rc = JS_SuspendRequest(cx);
	trace("%s", str);
JS_ResumeRequest(cx, rc);
	JS_SET_RVAL(cx, vp, JSVAL_VOID);
	return JS_TRUE;
}



//////////////////////////////////////////////////////////////////////////////
//
static JSBool
global_sleep(JSContext *cx, uintN argc, jsval *vp) {
	int n;
	if(!JS_ConvertArguments(cx, argc, JS_ARGV(cx, vp), "i", &n))
		return JS_FALSE;
	jsrefcount rc = JS_SuspendRequest(cx);
	PR_Sleep(PR_MillisecondsToInterval(n));
	JS_ResumeRequest(cx, rc);
	JS_SET_RVAL(cx, vp, JSVAL_VOID);
	return JS_TRUE;
}



//////////////////////////////////////////////////////////////////////////////
//
static JSBool
global_GC(JSContext *cx, uintN argc, jsval *vp) {
	JS_GC(cx);
	JS_SET_RVAL(cx, vp, JSVAL_VOID);
	return JS_TRUE;
}


#ifdef DEBUG
void dumpRoot(const char *name, void *rp, void *data) {
	/* The application may use `data` for anything.  In this
		example, we use it to pass the desired output file. */
	FILE *f = (FILE *) data;
	fprintf(f, "There is a root named '%s' at %p\n", name, rp);
}
                   

static JSBool
global_dumpGC(JSContext *cx, uintN argc, jsval *vp) {
	console_set_lock(1);
	FILE *out = stdout;
	JS_DumpHeap(cx, out, NULL, 0, NULL, 10, NULL);
	fprintf(out, "\n");
	JS_DumpNamedRoots(rt, dumpRoot, (void *)out);
	console_set_lock(0);
	JS_SET_RVAL(cx, vp, JSVAL_VOID);
	return JS_TRUE;
}
#endif

//////////////////////////////////////////////////////////////////////////////
//
static JSBool
global_getThreadId(JSContext *cx, uintN argc, jsval *vp) {
	//int n;
	//if(!JS_ConvertArguments(cx, argc, JS_ARGV(cx, vp), "i", &n))
	//	return JS_FALSE;
	js_thread_t *x = (js_thread_t *)JS_GetContextPrivate(cx);
	JS_SET_RVAL(cx, vp, x ? INT_TO_JSVAL(x->id) : JSVAL_VOID);	
	return JS_TRUE;
}



//////////////////////////////////////////////////////////////////////////////
//
static JSBool
global_exit(JSContext *cx, uintN argc, jsval *vp) {
	int n;
	if(!JS_ConvertArguments(cx, argc, JS_ARGV(cx, vp), "i", &n))
		return JS_FALSE;
	exit(n);
	JS_SET_RVAL(cx, vp, JSVAL_VOID);
	return JS_TRUE;
}


static JSBool
global_load(JSContext *cx, uintN argc, jsval *vp) {
	trace1("load");
	char *x;
	if (!JS_ConvertArguments(cx, argc, JS_ARGV(cx, vp), "s", &x))
		return JS_FALSE;
	jsval rval;
	if (!eval_file(cx, x, 0, &rval)) {
		trace2("failed to load %s", x);
		return JS_FALSE;
	}
	free(x);
	JS_SET_RVAL(cx, vp, rval);
	return JS_TRUE;	
}



/**
 *
 */
static JSBool
global_make(JSContext *cx, uintN argc, jsval *vp) {	
	trace1("js_make");
	jsval func;

	if (!JS_ConvertValue(cx, JS_ARGV(cx, vp)[0], JSTYPE_FUNCTION, &func))
		return JS_FALSE;

	uint32 n, i;
	JS_ConvertArguments(cx, argc, JS_ARGV(cx, vp)+1, "u", &n);

js_thread_t *x;

//JS_BeginRequest(cx);
	PRThread *tid = NULL, *tid0=NULL;
	for (i=0; i<n; ++i){
		x = get_thread_slot(cx);
		x->func = func;
		x->argc = 1;
		x->argv[0] = INT_TO_JSVAL(i);
		if (tid)
			x->join = tid;
		tid = PR_CreateThread(
			PR_USER_THREAD,
			js_thread_proc,
			(void *)x,
			PR_PRIORITY_NORMAL,
			PR_LOCAL_THREAD,
			PR_JOINABLE_THREAD,
			0
		);
	}
	jsrefcount rc = JS_SuspendRequest(cx);
	PR_JoinThread(tid);
	JS_ResumeRequest(cx, rc);
//JS_EndRequest(cx);

	return JS_TRUE;
}




static JSBool
global_import(JSContext *cx, uintN argc, jsval *vp) {
	trace1("load");
	char *name;
	if (!JS_ConvertArguments(cx, argc, JS_ARGV(cx, vp), "s", &name))
		return JS_FALSE;
	
	
	//char *lib_name = PR_GetLibraryName(PR_GetLibraryPath(), name);
	
	char lib_name[1000];
	getcwd(lib_name, 1000);
	strcat(lib_name, "/");
	strcat(lib_name, name);
	char funcname[] = "init";
	
	trace2("lib_name=%s", lib_name);

/*	
#ifdef WITH_NSPR
	PRLibrary *lib = PR_LoadLibrary(lib_name);
	if (!lib) {
		char err[1000];
		PR_GetErrorText(err);
		JS_ReportError(cx, "PR_LoadLibrary returned error: %s.", err);
		return JS_FALSE;
	}
	init_proc *init = (init_proc *)PR_FindSymbol(lib, "init");	
#else
*/
	//void *lib = dlopen(lib_name, RTLD_LAZY);
	
	errno = 0;
	
	void *lib = dlopen(lib_name, RTLD_GLOBAL);
	if(!lib) {
		JS_ReportError(cx, "dlopen returned error: %s.", dlerror());
		return JS_FALSE;
	}
	init_proc init = (init_proc)dlsym(lib, funcname);
	char *err = dlerror();
	if (err)
		fatal("dl error: %s", err);
/*	
#endif	
*/

	if(!init) {
		JS_ReportError(cx, "%s: function %s not found", name, funcname);
		return JS_FALSE;
	}

	JSContext *cx2;
	//cx2 = create_context(cx);
	cx2 = cx;
	trace2("passing context cx2=%p", cx2);
	
JS_BeginRequest(cx2);
	init(cx2, rt);
JS_EndRequest(cx2);
	
	dlclose(lib);
	

	JS_SET_RVAL(cx, vp, JSVAL_VOID);
	return JS_TRUE;	
}





//#define MT_THREADS 5





static JSClass global_class = {
    "global", JSCLASS_GLOBAL_FLAGS,
    JS_PropertyStub, JS_PropertyStub, JS_PropertyStub,


#if JS_VERSION > 180
    JS_StrictPropertyStub,
#else
    JS_PropertyStub,
#endif
    
    
    JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, JS_FinalizeStub,
    JSCLASS_NO_OPTIONAL_MEMBERS
};


static JSFunctionSpec global_functions[] = {
//    JS_FN("tcpsocket",  global_tcpsocket,  2,0),

    JS_FN("make"        , global_make        , 2, 0),
	JS_FN("alert"       , global_print       , 1, 0),
	JS_FN("print"       , global_print       , 1, 0),
	JS_FN("trace"       , global_trace       , 1, 0),
	JS_FN("basename"    , global_basename    , 1, 0),
	JS_FN("dirname"     , global_dirname     , 1, 0),
	JS_FN("chdir"       , global_chdir       , 1, 0),
	JS_FN("getcwd"      , global_getcwd      , 1, 0),
	JS_FN("sleep"       , global_sleep       , 1, 0),
	JS_FN("exit"        , global_exit        , 1, 0),
	JS_FN("GC"          , global_GC          , 0, 0),
	JS_FN("getThreadId" , global_getThreadId , 0, 0),
	JS_FN("load"        , global_load        , 1, 0),
	JS_FN("import"      , global_import      , 1, 0),
    JS_FN("setTimeout"  , global_setTimeout  , 2, 0),
    JS_FN("clearTimeout", global_clearTimeout, 1, 0),
#ifdef DEBUG
	JS_FN("dumpGC"      , global_dumpGC,  0, 0),
#endif	
    JS_FS_END
};



//////////////////////////////////////////////////////////////////////////////
//
void class_global_init(JSContext *cx){
	JSObject *global;

#if JS_VERSION > 180
	JS_BeginRequest(cx);
		global = JS_NewCompartmentAndGlobalObject(cx, &global_class, NULL);
		JSCrossCompartmentCall *ccc = JS_EnterCrossCompartmentCall(cx, global);
		JS_InitStandardClasses(cx, global);
		JS_DefineFunctions(cx, global, global_functions);
		JS_LeaveCrossCompartmentCall(ccc);	

	JS_EndRequest(cx);
#else
	JS_BeginRequest(cx);
		global = JS_NewObject(cx, &global_class, NULL, NULL);
		JS_DefineFunctions(cx, global, global_functions);
		JS_InitStandardClasses(cx, global);
		JS_SetGlobalObject(cx, global);		
	JS_EndRequest(cx);
#endif

/*

	JSObject *obj = JS_NewObject(cx, &global_class, NULL, NULL);	
	JS_DefineFunctions(cx, obj, my_functions);
	JS_InitStandardClasses(cx, obj);
	JS_SetGlobalObject(cx, obj);
*/
	
	//global_print_lock = PR_NewLock();
}




